/*
 * Copyright (C) 2011 Julia Mlinnik
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.jmlinnik.android.passwdkeyboard;

import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.Keyboard.Key;
import android.inputmethodservice.KeyboardView;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;

public class PasswdKeyboard extends InputMethodService implements
        KeyboardView.OnKeyboardActionListener {

    private KeyboardView mInputView;
    private Keyboard mQwertyKeyboard;
    private Keyboard mSymbolsKeyboard;
    private Key mShiftKey;

    private Drawable mShiftIcon;
    private Drawable mShiftShiftedIcon;

    private static final int SHIFT_OFF = 0;
    private static final int SHIFT_ON = 1;
    private static final int SHIFT_LOCKED = 2;

    private int mShiftState = SHIFT_OFF;

    private int mLastDisplayWidth;

    private StringBuilder mComposing = new StringBuilder();

    /**
     * Called by the framework when your view for creating input needs to be
     * generated. This will be called the first time your input method is
     * displayed, and every time it needs to be re-created such as due to a
     * configuration change.
     */
    @Override
    public View onCreateInputView() {
        mInputView = (KeyboardView) getLayoutInflater().inflate(R.layout.input,
                null);
        mInputView.setOnKeyboardActionListener(this);
        mInputView.setKeyboard(mQwertyKeyboard);
        return mInputView;
    }

    /**
     * This is the point where you can do all of your UI initialization. It is
     * called after creation and any configuration change.
     */
    @Override
    public void onInitializeInterface() {
        if (mQwertyKeyboard != null) {
            // Configuration changes can happen after the keyboard gets
            // recreated,
            // so we need to be able to re-build the keyboards if the available
            // space has changed.
            int displayWidth = getMaxWidth();
            if (displayWidth == mLastDisplayWidth)
                return;
            mLastDisplayWidth = displayWidth;
        }
        mQwertyKeyboard = new Keyboard(this, R.xml.qwerty);
        mSymbolsKeyboard = new Keyboard(this, R.xml.symbols);
        mShiftKey = mQwertyKeyboard.getKeys().get(
                mQwertyKeyboard.getShiftKeyIndex());

        final Resources res = getResources();
        mShiftIcon = res.getDrawable(R.drawable.sym_keyboard_shift);
        mShiftShiftedIcon = res
                .getDrawable(R.drawable.sym_keyboard_shift_locked);
    }

    /**
     * This is the main point where we do our initialization of the input method
     * to begin operating on an application. At this point we have been bound to
     * the client, and are now receiving all of the detailed information about
     * the target of our edits.
     */
    @Override
    public void onStartInput(EditorInfo attribute, boolean restarting) {
        super.onStartInput(attribute, restarting);

        // Reset our state. We want to do this even if restarting, because
        // the underlying state of the text editor could have changed in any
        // way.
        mComposing.setLength(0);

        mShiftState = SHIFT_OFF;

        updateShiftKeyState();

    }

    /**
     * This is called when the user is done editing a field. We can use this to
     * reset our state.
     */
    @Override
    public void onFinishInput() {
        super.onFinishInput();

        // Clear current composing text and candidates.
        mComposing.setLength(0);

        if (mInputView != null) {
            mInputView.closing();
        }
    }

    @Override
    public void onStartInputView(EditorInfo attribute, boolean restarting) {
        super.onStartInputView(attribute, restarting);
        // Apply the selected keyboard to the input view.
        mInputView.setKeyboard(mQwertyKeyboard);
        mInputView.closing();
    }

    private void handleShift() {
        if (mInputView == null) {
            return;
        }

        setShiftState();

        mInputView.setShifted(mShiftState != SHIFT_OFF);
        updateShiftIcon(mShiftState != SHIFT_OFF);
        setShiftLocked(mShiftState == SHIFT_LOCKED);

    }

    private void setShiftState() {
        switch (mShiftState) {
        case SHIFT_OFF:
            mShiftState = SHIFT_ON;
            break;
        case SHIFT_ON:
            mShiftState = SHIFT_LOCKED;
            break;
        case SHIFT_LOCKED:
            mShiftState = SHIFT_OFF;
            break;
        }

    }

    private void updateShiftIcon(boolean shiftOn) {
        mShiftKey.icon = shiftOn ? mShiftShiftedIcon : mShiftIcon;
    }

    void setShiftLocked(boolean shiftLocked) {
        mShiftKey.on = shiftLocked;
    }

    @Override
    public void onKey(int primaryCode, int[] keyCodes) {
        if (primaryCode == Keyboard.KEYCODE_DELETE) {
            handleBackspace();
        } else if (primaryCode == Keyboard.KEYCODE_SHIFT) {
            handleShift();
        } else if (primaryCode == Keyboard.KEYCODE_CANCEL) {
            handleClose();
            return;
        } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE
                && mInputView != null) {
            Keyboard current = mInputView.getKeyboard();
            if (current == mSymbolsKeyboard) {
                current = mQwertyKeyboard;
            } else {
                current.setShifted(false);
                mShiftState = SHIFT_OFF;
                updateShiftIcon(false);
                current = mSymbolsKeyboard;
            }

            mInputView.setKeyboard(current);
        } else {
            handleCharacter(primaryCode, keyCodes);
        }
    }

    /**
     * Use this to monitor key events being delivered to the application. We get
     * first crack at them, and can either resume them or let them continue to
     * the app.
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        switch (keyCode) {
        case KeyEvent.KEYCODE_BACK:
            // The InputMethodService already takes care of the back
            // key for us, to dismiss the input method if it is shown.
            // However, our keyboard could be showing a pop-up window
            // that back should dismiss, so we first allow it to do that.
            if (event.getRepeatCount() == 0 && mInputView != null) {
                if (mInputView.handleBack()) {
                    return true;
                }
            }
            break;

        case KeyEvent.KEYCODE_DEL:
            // Special handling of the delete key: if we currently are
            // composing text for the user, we want to modify that instead
            // of let the application to the delete itself.
            if (mComposing.length() > 0) {
                onKey(Keyboard.KEYCODE_DELETE, null);
                return true;
            }
            break;

        }

        return super.onKeyDown(keyCode, event);
    }

    private void handleCharacter(int primaryCode, int[] keyCodes) {
        if (isInputViewShown()) {
            if (mInputView.isShifted()) {
                primaryCode = Character.toUpperCase(primaryCode);
            }
        }

        if (isAlphabet(primaryCode)) {
            updateShiftKeyState();
        }
        getCurrentInputConnection().commitText(
                String.valueOf((char) primaryCode), 1);

    }

    private void handleClose() {
        commitTyped(getCurrentInputConnection());
        requestHideSelf(0);
        mInputView.closing();
    }

    private void handleBackspace() {
        final int length = mComposing.length();
        if (length > 1) {
            mComposing.delete(length - 1, length);
            getCurrentInputConnection().setComposingText(mComposing, 1);
        } else if (length > 0) {
            mComposing.setLength(0);
            getCurrentInputConnection().commitText("", 0);
        } else {
            keyDownUp(KeyEvent.KEYCODE_DEL);
        }

        updateShiftKeyState();
    }

    @Override
    public void onPress(int arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onRelease(int arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onText(CharSequence text) {
        InputConnection ic = getCurrentInputConnection();
        if (ic == null)
            return;
        ic.beginBatchEdit();
        if (mComposing.length() > 0) {
            commitTyped(ic);
        }
        ic.commitText(text, 0);
        ic.endBatchEdit();
        // updateShiftKeyState(getCurrentInputEditorInfo());
        updateShiftKeyState();
    }

    @Override
    public void swipeDown() {
        handleClose();
    }

    @Override
    public void swipeLeft() {
        handleBackspace();
    }

    @Override
    public void swipeRight() {
        // TODO Auto-generated method stub

    }

    @Override
    public void swipeUp() {
        // TODO Auto-generated method stub

    }

    private void updateShiftKeyState() {
        if (mInputView != null && mQwertyKeyboard == mInputView.getKeyboard()) {
            mInputView.setShifted(mShiftState == SHIFT_LOCKED);
            mShiftState = (mShiftState == SHIFT_LOCKED) ? SHIFT_LOCKED
                    : SHIFT_OFF;
            updateShiftIcon(mShiftState == SHIFT_LOCKED);
        }
    }

    /**
     * Helper to send a key down / key up pair to the current editor.
     */
    private void keyDownUp(int keyEventCode) {
        getCurrentInputConnection().sendKeyEvent(
                new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode));
        getCurrentInputConnection().sendKeyEvent(
                new KeyEvent(KeyEvent.ACTION_UP, keyEventCode));
    }

    /**
     * Helper function to commit any text being composed in to the editor.
     */
    private void commitTyped(InputConnection inputConnection) {
        if (mComposing.length() > 0) {
            inputConnection.commitText(mComposing, mComposing.length());
            mComposing.setLength(0);
        }
    }

    /**
     * Helper to determine if a given character code is alphabetic.
     */
    private boolean isAlphabet(int code) {
        if (Character.isLetter(code)) {
            return true;
        } else {
            return false;
        }
    }

}
